home *** CD-ROM | disk | FTP | other *** search
/ Windows News 2010 Summer - Disc 1 / WN_Ete2010_CD1.iso / Onglet5 / Weezo / Weezo setup.exe / {code_appDir} / www / includes / miscFunctions.php < prev    next >
PHP Script  |  2010-05-19  |  31KB  |  867 lines

  1. <?php
  2. /**
  3.  * Miscellaneous functions
  4.  *
  5.  * PHP version 5
  6.  *
  7.  * LICENSE: This source file is subject to version 3.0 of the PHP license
  8.  * that is available through the world-wide-web at the following URI:
  9.  * http://www.php.net/license/3_0.txt.  If you did not receive a copy of
  10.  * the PHP License and are unable to obtain it through the web, please
  11.  * send a note to license@php.net so we can mail you a copy immediately.
  12.  *
  13.  * @category   NA
  14.  * @package    NA
  15.  * @author     Nicolas Bruley / Peer 2 World <contact@weezo.net>
  16.  * @copyright  2005-2009 Nicolas Bruley / Peer 2 World
  17.  * @license    http://www.php.net/license/3_0.txt  PHP License 3.0
  18.  * @version    CVS: $Id:$
  19.  * @link       http://www.weezo.net
  20.  * @since      File available since Release 1.0.7G
  21.  */
  22.  
  23. define('MF_LOADED',1);     // used to replace require_once
  24.  
  25. define('READ_BUFFER_SIZE',32384);     // Default amount of data read from file and put into read buffer used for mp3 parsing
  26. define('MAX_READ_BUFFER_LENGTH',3238400); // Max amount of data in read buffer
  27. define('MAX_PARSED_MP3FILE_SIZE',32000000); // Max amount of data in read buffer
  28.  
  29.  
  30. /**
  31.  * @desc Download favicons for a list of URLs
  32.  *
  33.  * @param array $urlsList: array of urls
  34.  * @param array $options
  35.  *                 maxRequests: max simultaneous HTTP requests (30)
  36.  *                 toFile=>dir: save result icons to file into dir, using base64encoded(url).ico.format as filename
  37.  *                 HTMLParsing=>true: true(default) to parse HTML for <link rel...
  38.  * @return array (k=>array('format'=>'png'||'gif'||'jpg', 'data'=>image bin content)
  39.  */
  40. function mfGetFavicons($urlsList,$options=array()){
  41.     $favicons=array();
  42.     require_once(INCLUDE_DIR.'explorerFunctions.php');
  43.  
  44.     // If synchronous monitoring set, add callback to cfCURLMultiRequests
  45.     if(isset($_ENV['outSyncProgress'])) $_ENV['mfGetFaviconsCB']=array('callback'=>'syncProgressCallback'); else $_ENV['mfGetFaviconsCB']=array();
  46.  
  47. //$urlsList=array('http://www.meteofrance.com/FR/mameteo/prevReg.jsp?LIEUID=REG12&ECHEANCEID=200506041200');
  48.  
  49.     // Sync update progress
  50.     function syncProgressCallback($nb,$time){outSyncProgressUpdate($nb+$_ENV['mfGetFaviconsOffset']+$_ENV['mfGetFaviconsOffsetSL']);}
  51.  
  52.     // Get favicon for 'maxRequests' urls
  53.     function mfGetFaviconsSL($urls, $options){
  54.         $result=array(); $base=array();
  55.         $_ENV['mfGetFaviconsOffsetSL']=0;
  56.  
  57.         // Get /favicon.ico's
  58.         foreach ($urls as $v){
  59.             if(strtolower(substr($v,0,4))!='http') $url=parse_url('http://'.$v); else $url=parse_url($v);
  60.             $uris[]=$url['scheme'].'://'.$url['host'].((isset($url['port']))?':'.$url['port']:'').'/favicon.ico';
  61.         }
  62.  
  63.         // Download icons
  64.         $res=cfCURLMultiRequests($uris,array(CURLOPT_FAILONERROR=>0,CURLOPT_FOLLOWLOCATION=>1,CURLOPT_HEADER=>1,CURLOPT_MAXREDIRS=>1)+$_ENV['mfGetFaviconsCB'],$redirections);
  65.  
  66.         // Get favicons from meta tags
  67.         $metas=array();
  68.         foreach ($res as $k=>$v) {
  69.             if($v!==null){
  70.                 cfParseHTTPResponse($v,$status,$data,$headers);
  71.  
  72.                 // Icon found
  73.                 if($status==200 && isset($headers['content-type']) && cfCmpLeft($headers['content-type'],'image')){
  74.                     $ct=substr($headers['content-type'],6);
  75.                     if($ct=='gif'||$ct=='png'||$ct=='jpg'||$ct=='jpeg') $icons[$k]=array('format'=>$ct,'data'=>$data); else $icons[$k]=array('format'=>'ico','raw'=>$data);
  76.                 }
  77.                 // Else, choose between follow redirection or parse html
  78.                 else {
  79.                     if(isset($redirections[$k])) $metas[$k]=$redirections[$k]; else $metas[$k]=$urls[$k];
  80.                 }
  81.             }
  82.         }
  83.  
  84.         // Some icons couldn't be found, but do no HTML parsing (used for RSS feeds)
  85.         if(count($metas) && !$options['HTMLParsing']){}
  86.  
  87.         // If some icons couldn't be retreived, parse HTML pages
  88.         elseif(count($metas)){
  89.             $uris2=array();$redirections=array();
  90.  
  91.             // Retreive HTML pages
  92.             $_ENV['mfGetFaviconsOffsetSL']=count($urls)-count($metas);
  93.             $res2=cfCURLMultiRequests($metas,array(CURLOPT_FOLLOWLOCATION=>1,CURLOPT_HEADER=>1,CURLOPT_MAXREDIRS=>1)+$_ENV['mfGetFaviconsCB'],$redirections);
  94.  
  95.             // Parse HTML to find meta
  96.             foreach ($res2 as $k=>$v){
  97.                 if(($p=stripos($v,'<link rel="shortcut icon"')) || ($p=stripos($v,'<link rel="icon"'))){
  98.                     $txt=substr($v,stripos($v,'href="',$p)+6); $txt=substr($txt,0,stripos($txt,'"'));
  99.  
  100.                     if(substr(strtolower($txt),0,4)!=='http'){
  101.                         $base=(isset($redirections[$k]))?$redirections[$k]:$uris[$k];
  102.                         if($txt[0]=='/') $txt=substr($base,0,strlen($base)-12).$txt;
  103.                         else $txt=dirname($base).'/'.$txt;
  104.                     }
  105.                     $uris2[$k]=$txt;
  106.                 }
  107.             }
  108.             // Download found icons
  109.             if(count($uris2)){
  110.                 $_ENV['mfGetFaviconsOffsetSL']=count($urls)-count($uris2);
  111.                 $res2=cfCURLMultiRequests($uris2,array(CURLOPT_FOLLOWLOCATION=>1,CURLOPT_HEADER=>1,CURLOPT_MAXREDIRS=>1)+$_ENV['mfGetFaviconsCB']);
  112.  
  113.                 foreach ($res2 as $k=>$v) {
  114.                     cfParseHTTPResponse($v,$status,$data,$headers);
  115.                     if($status==200 /*&& isset($headers['content-type']) && cfCmpLeft($headers['content-type'],'image')*/){
  116.                         if(isset($headers['content-type']) && ($ct=substr($headers['content-type'],6)) &&
  117.                             ($ct=='gif'||$ct=='png'||$ct=='jpg'||$ct=='jpeg'))
  118.                             $icons[$k]=array('format'=>$ct,'data'=>$data);
  119.                         else $icons[$k]=array('format'=>'ico','raw'=>$data);
  120.                     }
  121.                     else $metas[$k]=$urls[$k];
  122.                 }
  123.             }
  124.         }
  125.  
  126.         // Convert .ico to .png
  127.         if(isset($icons)) foreach ($icons as $k=>$v) if ($v['format']=='ico') {
  128.             $tmpF=cfAppTempDir().'/ico_tmp'.$k.'.ico';
  129.             $prev=strlen((isset($v['raw']))?$v['raw']:$v['data']);
  130.             file_put_contents($tmpF,isset($v['raw'])?$v['raw']:$v['data']);
  131.             $icons[$k]['data']=efExtractIcon($tmpF,false,16,array('icoPngCheck'=>1));
  132.             $icons[$k]['format']='png';
  133.             //cfVarDump($k.' '.'>'.$prev.'>'.strlen($icons[$k]['data']));
  134.             unset($icons[$k]['raw']);
  135.             if(isset($tmpF)) @unlink($tmpF);
  136.         }
  137.  
  138.         foreach ($urls as $k=>$v) if(!isset($icons[$k])) $icons[$k]=array('format'=>'no','data'=>false);
  139.  
  140.         foreach ($urls as $k=>$v) $icons[$k]['url']=$urls[$k];
  141.  
  142.         return $icons;
  143.     }
  144.  
  145.  
  146.     $options=$options+array('maxRequests'=>30,'HTMLParsing'=>true);
  147.     $maxRequest=$options['maxRequests'];
  148.     $nb=0;
  149.     $tmpUrls=array();
  150.     $_ENV['mfGetFaviconsOffset']=0;
  151.  
  152.     // Split requests into groups of $maxRequest
  153.     while(count($urlsList)){
  154.         $tmpUrls[]=array_shift($urlsList);
  155.         if((++$nb)==$maxRequest || !count($urlsList)) {
  156.             foreach (mfGetFaviconsSL($tmpUrls,$options) as $v) $favicons[]=$v;
  157.             $nb=0;    $tmpUrls=array();
  158.             $_ENV['mfGetFaviconsOffset']+=$maxRequest;
  159.         }
  160.     }
  161.  
  162.     // Save result to files
  163.     if(isset($options['toFile'])){
  164.         $dir=$options['toFile'];
  165.         foreach ($favicons as $k=>$v){
  166.             //echo $v['url'].(($v['format']=='no')?'NO TN':':<img src="data:image/'.$v['format'].';base64,'.base64_encode($v['data']).'">').'<br>';
  167.             $cfn=$dir.'/'.md5($v['url']).'.ico.'.$v['format'];
  168.             file_put_contents($cfn,$v['data']);
  169.         }
  170.         return count($favicons);
  171.     }
  172.  
  173.     return $favicons;
  174. }
  175.  
  176. /**
  177.  * @desc Return url's favicon filename, false if not generated, 'noIcon' if favicon has not been found for this url
  178.  *
  179.  * @param string $url
  180.  * @return mixed string / false
  181.  */
  182. function mfFaviconFile($url){
  183.     $cfn=cfAppResourceDir().'/'.md5($url);
  184.     foreach (array('png','gif','jpg','jpeg','no') as $v)
  185.         if(file_exists($cfn.'.ico.'.$v)) return (($v=='no')?'noIcon':$cfn.'.ico.'.$v);
  186.     return false;
  187. }
  188.  
  189. /**
  190.  * @desc Return url's favicon filename, false if not generated, 'noIcon' if favicon has not been found for this url
  191.  *
  192.  * @param string $url
  193.  * @return mixed string / false
  194.  */
  195. function mfFaviconLink($url){
  196.     $ico=mfFaviconFile($url);
  197.     if($ico=='noIcon') return outIcon('blankFile'); else return cfExtImage($ico);
  198. }
  199.  
  200.  
  201. /**
  202.  * @desc Download a file, and synchronously display progress bar
  203.  *
  204.  * @param string $url: URL of file to download
  205.  * @param string $destFile: destination file
  206.  * @param array $options: array of progress page display options (caption,icon,standalone,display,suspendSession, appendHTML (HTML appened after progress bar), cancelHref (href of cancel button, if not set, no cancel button)...)
  207.  */
  208. function mfMonitoredDownload($url,$destFile,$options=array()){
  209.     require_once(INCLUDE_DIR.'outputFunctions.php');
  210.  
  211.     // Insert progress monitoring HTML stuff
  212.     outSyncProgress(@$options['caption'],@$options['icon'],100,((isset($options['standalone']))?$options['standalone']:2),$options);
  213.     if (isset($options['cancelHref'])) echo '<br>'.outButton(cfCaption('genCancel'),$options['cancelHref'],outIcon('cancel')).'<br>';
  214.     if(isset($options['appendHTML'])) echo $options['appendHTML'];
  215.     outSyncProgressUpdate(0,false);
  216.     flush();
  217.  
  218.     // Max 3 redirections
  219.     $proceed=4;
  220.     while($proceed){
  221.         if(!($handle=cfSocketHTTPRequest($url,7,false,$foo1,$foo2,false,true))) return false;
  222.         $response='';
  223.         while(strlen($response)<1023 && !feof($handle)) $response .= fread($handle, 1024);
  224.  
  225.         $pos=strpos($response,"\r\n\r\n");
  226.         if($pos === false){
  227.             fclose($handle);
  228.             outSyncProgressUpdate('done');
  229.             return false;
  230.         }
  231.         $header=substr($response,0,$pos);
  232.         $response=substr($response,$pos+4);
  233.  
  234.         $headers = array();
  235.         $lines = explode("\r\n", $header);
  236.         $fSize=0;
  237.         if(count($lines)==0) {outSyncProgressUpdate('done');return false;}
  238.  
  239.         // HTTP response code
  240.         $responseCode=substr($lines[0],9,3);
  241.  
  242.         // Seek file size or redirection
  243.         foreach($lines as $value){
  244.             if(strtolower(substr($value,0,15))=='content-length:') $fSize=trim(substr($value,15));
  245.             if(strtolower(substr($value,0,9))=='location:') $redir=trim(substr($value,9));
  246.         }
  247.         // If redirection
  248.         if(isset($redir)) {
  249.             @fclose($handle);
  250.             $proceed--;
  251.             if($proceed<=0) {
  252.                 outSyncProgressUpdate('done');
  253.                 return false;
  254.             }
  255.             $updateUri=$redir;
  256.             if(substr($redir,0,1)=='/') {
  257.                 $url=parse_url($url);
  258.                 $url=$url['scheme'].'://'.$url['host'].((isset($url['port']))?':'.$url['port']:'').$redir;
  259.             }
  260.             else $url=$redir;
  261.             unset($redir);
  262.         }
  263.         // ! 200 OK
  264.         elseif($responseCode!='200') {outSyncProgressUpdate('done');return false;}
  265.         // No redirection and response code 200 OK
  266.         else $proceed=0;
  267.     }
  268.  
  269.     // Proceed Download
  270.     $handlew = fopen ($destFile, "w");
  271.     if(!$handlew) {fclose($handle);outSyncProgressUpdate('done');return false;}
  272.     $i=-1;
  273.     fwrite($handlew,$response);
  274.     $dlSize=strlen($response);
  275.  
  276.     // Ignore user abort to detect clicks on cancel button
  277.     ignore_user_abort(true);
  278.  
  279.     while (!feof($handle)) {
  280.         $response=fread($handle, 8192);
  281.         fwrite($handlew,$response);
  282.         $i++;$dlSize+=strlen($response);
  283.  
  284.         if($i%4==0) outSyncProgressUpdate($dlSize,$fSize);
  285.  
  286.         // If user aborted, delete destination file and exit
  287.         if(connection_aborted()){
  288.             fclose($handle);
  289.             fclose($handlew);
  290.             @unlink($destFile);
  291.             exit;
  292.         }
  293.     }
  294.     fclose($handlew);
  295.     fclose($handle);
  296.  
  297.     outSyncProgressUpdate('done');
  298.  
  299.     return $dlSize;
  300. }
  301.  
  302.  
  303. /**
  304.  * @Desc Class used to retreive frames of ID3V2 tagged mp3.
  305.  * Check http://www.mp3-tech.org/ or http://www.datavoyage.com/mpgscript/mpeghdr.htm for info about format
  306.  *
  307.  */
  308. class id3V2Ext{
  309.     public $version; // ID3 version
  310.     public $tag=array(); // Contained tags array
  311.     private $data; // (start of file content)
  312.     private $dataLength; // Length of data
  313.     private $framesNb=0; // Number of mp3 frames (used for vbr calculations)
  314.     public $br; // Bitrate (b/s)
  315.     public $vbr=0; // Boolean: true if vbr
  316.     private $fp; // File pointer
  317.     private $fileSize; // mp3 file size
  318.     private $firstFramePos=false; // position of 1st mp3 frame
  319.     private $offset=0; // Total length of data removed from the begining of buffer
  320.  
  321.     static $frames=array('CRA'=>'audioEncr','AENC'=>'audioEncr','PIC'=>'attPict','APIC'=>'attPict','ASPI'=>'audioSeekPntIdx','COM'=>'comment','COMM'=>'comment','COMR'=>'commercial','ENCR'=>'encrMethodReg','EQU'=>'equalisation','EQUA'=>'equalisation','EQU2'=>'equalisation2','ETC'=>'eventTimingCodes','ETCO'=>'eventTimingCodes','GEO'=>'encObj','GEOB'=>'encObj','GRID'=>'grpIdReg','LNK'=>'lnkInf','LINK'=>'lnkInf','MCI'=>'cdId','MCDI'=>'cdId','MLL'=>'mpgLocLookupTbl','MLLT'=>'mpgLocLookupTbl','OWNE'=>'ownership','PRIV'=>'private','CNT'=>'playCnt','PCNT'=>'playCnt','POP'=>'popularimeter','POPM'=>'popularimeter','POSS'=>'posSynch','BUF'=>'recmdBufSize','RBUF'=>'recmdBufSize','RVA'=>'relVolAdj','RVAD'=>'relVolAdj','RVA2'=>'relVolAdj2','REV'=>'reverb','RVRB'=>'reverb','SEEK'=>'seek','SIGN'=>'signature','SLT'=>'synchLyric','SYLT'=>'synchLyric','STC'=>'synchTempoCode','SYTC'=>'synchTempoCode','TAL'=>'album','TALB'=>'album','TBP'=>'bpm','TBPM'=>'bpm','TCM'=>'composer','TCOM'=>'composer','TCO'=>'genre','TCON'=>'genre','TCR'=>'copyright','TCOP'=>'copyright','TDEN'=>'encodingTime','TDLY'=>'playlistDelay','TDOR'=>'orgReleaseTime','TDRC'=>'recTime','TDRL'=>'releaseTime','TDTG'=>'taggingTime','TEN'=>'encodedBy','TENC'=>'encodedBy','TXT'=>'lyricist','TEXT'=>'lyricist','TFLT'=>'filetype','TIPL'=>'involvedPeopleList','TT1'=>'description','TIT1'=>'description','TT2'=>'title','TIT2'=>'title','TT3'=>'subtitle','TIT3'=>'subtitle','TKEY'=>'initialKey','TLA'=>'language','TLAN'=>'language','TLE'=>'length','TLEN'=>'length','TMCL'=>'musicianCreditsList','TMED'=>'mediaType','TMOO'=>'mood','TOT'=>'originalAlbum','TOAL'=>'originalAlbum','TOF'=>'originalFilename','TOFN'=>'originalFilename','TOL'=>'originalLyricist','TOLY'=>'originalLyricist','TOA'=>'originalArtist','TOPE'=>'originalArtist','TOWN'=>'fileOwner','TP1'=>'artist','TPE1'=>'artist','TP2'=>'band','TPE2'=>'band','TP3'=>'conductor','TPE3'=>'conductor','TP4'=>'remixer','TPE4'=>'remixer','TPOS'=>'partOfASet','TPRO'=>'producedNotice','TPB'=>'publisher','TPUB'=>'publisher','TRK'=>'track','TRCK'=>'track','TRSN'=>'IRSName','TRSO'=>'IRSOwner','TSI'=>'size','TSIZ'=>'size','TSOA'=>'albumSortOrder','TSOP'=>'performerSortOrder','TSOT'=>'titleSortOrder','TRC'=>'isrc','TSRC'=>'isrc','TSS'=>'encoderSettings','TSSE'=>'encoderSettings','TSST'=>'setSubtitle','TXX'=>'text','TXXX'=>'text','TYE'=>'year','TYER'=>'year','UFI'=>'uniqueFileId','UFID'=>'uniqueFileId','USER'=>'termsOfUse','ULT'=>'unsynchLyricTranscr','USLT'=>'unsynchLyricTranscr','WCM'=>'commInfo','WCOM'=>'commInfo','WCP'=>'copyrightInfo','WCOP'=>'copyrightInfo','WAF'=>'webOffAudioFile','WOAF'=>'webOffAudioFile','WAR'=>'webOffArtist','WOAR'=>'webOffArtist','WAS'=>'webOffAudioSrc','WOAS'=>'webOffAudioSrc','WORS'=>'webOffIRS','WPAY'=>'payment','WPB'=>'webOffPubl','WPUB'=>'webOffPubl','WXX'=>'webUserdef','WXXX'=>'webUserdef'); // prod
  322.  
  323.     // Add data to buffer
  324.     private function readData($size=READ_BUFFER_SIZE){
  325.         if($size=='all') $size=$this->fileSize;
  326.         if($size>$_ENV['memory_limit']) return false;
  327.         $this->data.=fread($this->fp,$size);
  328.         $this->dataLength=strlen($this->data);
  329.         return true;
  330.     }
  331.  
  332.     /**
  333.      * Read buffer, and automatically read and flush unused data (RUF)
  334.      * @param $from: absolute position from begining of file
  335.      * @param $length: length of requested data
  336.      */
  337.     private function getData($from,$length){
  338.         // Not enough data into buffer: refill
  339.         if($this->dataLength<$from+$length) {
  340.             $this->readData(max(READ_BUFFER_SIZE,$from+$length-$this->dataLength));
  341.             
  342.             // Destroy the begining of buffer if overall size is too large
  343.             if($this->dataLength-$this->offset>MAX_READ_BUFFER_LENGTH){
  344.                 $this->data=substr($this->data,$from-$this->offset);
  345.                 $this->offset=$from;
  346.             }
  347.         }
  348.  
  349.         // Return requested data
  350.         return substr($this->data,$from-$this->offset,$length);
  351.     }
  352.  
  353.     /**
  354.      * Parse mp3 frame header
  355.      *
  356.      * @param string $header4B: 4 bytes header
  357.      * @param integer $pos
  358.      * @return array of properties if OK, false if not an header
  359.      */
  360.     private function parseHeader($header4B,$pos){
  361.         static $freqs=array(2.5=>array('00'=>11025,'01'=>12000,'10'=>8000),
  362.                     2=>array('00'=>22050,'01'=>24000,'10'=>16000),
  363.                     1=>array('00'=>44100,'01'=>48000,'10'=>32000));
  364.         static $bitrates=array(
  365.                     '11'=>array(-1,32,64,96,128,164,192,224,256,288,320,352,384,416,448,-1),
  366.                     '12'=>array(-1,32,48,56,64,80,96,112,128,160,192,224,256,320,384,-1),
  367.                     '13'=>array(-1,32,40,48,56,64,80,96,112,128,160,192,224,256,320,-1),
  368.                     '21'=>array(-1,32,48,56,64,80,96,112,128,144,160,176,192,224,256,-1),
  369.                     '22'=>array(-1,8,16,24,32,40,48,56,64,80,96,112,128,144,160,-1),
  370.                     '23'=>array(-1,8,16,24,32,40,48,56,64,80,96,112,128,144,160,-1)
  371.                     );
  372.         static $fields;
  373.         static $header;
  374.  
  375.         // Convert header into bits string
  376.         $header='';    for($i=0;$i<4;$i++)    $header.=str_pad(decbin(ord($header4B[$i])),8,'0',STR_PAD_LEFT);
  377.  
  378.         // Frame sync
  379.         if(substr($header,0,11)!='11111111111') return false;
  380.         $fields=array();
  381.  
  382.         // MPEG Audio version ID
  383.         $fields['vid']=((substr($header,11,2)=='00')?2.5:((substr($header,11,2)==10)?2:1));
  384.  
  385.         // Layer description
  386.         $fields['layer']=((substr($header,13,2)=='01')?3:((substr($header,11,2)==10)?2:1));
  387.  
  388.         // 16bits CRC after header ?
  389.         $fields['crc']=1-(int)$header[15];
  390.  
  391.         // Sampling rate frequency
  392.         $fields['freq']=substr($header,20,2);
  393.         if(!isset($freqs[$fields['vid']][$fields['freq']])) return false;
  394.         $fields['freq']=$freqs[$fields['vid']][$fields['freq']];
  395.  
  396.         // Padding
  397.         $fields['pad']=$header[22];
  398.  
  399.         // Bitrate
  400.         $fields['br']=bindec(substr($header,16,4));
  401.         $index=floor($fields['vid']).$fields['layer'];
  402.         if(!isset($bitrates[$index][$fields['br']])) return false;
  403.         $fields['br']=1000*$bitrates[$index][$fields['br']];
  404.         if($fields['br']<=0) return false;
  405.  
  406.         // Compute frame length
  407.         if($fields['layer']==1) $fields['length']=(12*$fields['br']/$fields['freq']+(int)$fields['pad'])*4;
  408.         else $fields['length']=144*$fields['br']/$fields['freq']+(int)$fields['pad'];
  409.         $fields['length']=floor($fields['length']);
  410.  
  411.         if($fields['crc']) $fields['length']+=1;
  412.  
  413.         // Check for another header after frame length (just check 2 first bytes)
  414.         if($pos+$fields['length']+4<$this->fileSize){
  415.             // Read data from file if needed
  416.             //while($pos+$fields['length']+4>$this->dataLength && $this->dataLength<$this->fileSize) $this->readData();
  417.             if(substr($this->data,$pos+$fields['length'],2)!=substr($header4B,0,2)) return false;
  418.             $fields['nextPos']=$pos+$fields['length'];
  419.         }
  420.         else{
  421.             $fields['nextPos']=0;
  422.         }
  423.         return $fields;
  424.     }
  425.  
  426.  
  427.     /**
  428.      * @desc Generate tags array
  429.      *
  430.      * @param string $completeFilename: path and filname
  431.      * @param string $getOnlyTag: (optional) discard all tags not named (tag ID) like this
  432.      * @param boolean $process: (optional) decode tags, replace keys by key names
  433.      * @param boolean $vbrParsing: (optional) true to read all frames to compute track length and bitrate
  434.      * @return boolean: true on success
  435.      */
  436.     function __construct($completeFilename, $getOnlyTag=false,$process=false,$vbrParsing=false){
  437.         $this->tag=array();
  438.  
  439.         if(!($this->fp=@fopen($completeFilename,'rb'))) return false;
  440.  
  441.         // Set max read size, to prevent memory problems
  442.         $_ENV['memory_limit']=min(((int)str_replace('M','000000',ini_get('memory_limit')))/2,MAX_PARSED_MP3FILE_SIZE);
  443.  
  444.         // Read 1st block of data 65536 B
  445.         $this->readData();
  446.  
  447.         /**
  448.          * Parse file header
  449.          */
  450.         $header=substr($this->data,0,10);
  451.  
  452.         // Check for "ID3" tag
  453.         if(substr($header,0,3)!='ID3') {
  454.             // If ID3 tag not found, check for mp3 frame header - removed as some mp3 with no ID3 data may start by something else...
  455.             if(false && $this->parseHeader(substr($header,0,4),0)==false){
  456.                 fclose($this->fp);
  457.                 return false;
  458.             }
  459.             $this->fileSize=filesize($completeFilename);
  460.             $pos=-1;
  461.             $totalLength=0;
  462.         }
  463.         // "ID3" tag found, proceed
  464.         else{
  465.             $this->fileSize=filesize($completeFilename);
  466.             $flag = 0;
  467.             $this->version = ($majorVersion=ord($header[3])).'.'.ord($header[4]);
  468.             $flag = ord($header[5]);
  469.  
  470.             // Compute tag data length
  471.             $totalLength=($flag & 64)?-10:0;
  472.             $nb=0;
  473.  
  474.             for($i=0;$i<4;$i++)    $bytes[$i] = ord($header[6+$i]);
  475.             for($i=3;$i>=0;$i--) for($o=0;$o<7;$o++){
  476.                 if(($bytes[$i] & 1<<$o)==1<<$o) $totalLength+=1<<$nb;
  477.                 $nb++;
  478.             }
  479.             // Incorrect length, abort
  480.             if($totalLength<=0) {fclose($this->fp);$this->data='';return false;}
  481.             
  482.             // Put at least tag data length of data into buffer and extract tag
  483.             while ($totalLength>$this->dataLength) $this->readData();
  484.             $tagData = substr($this->data,10,$totalLength+4);
  485.             
  486.             $pos=0;
  487.  
  488.             /**
  489.              * Browse tag frames
  490.              */
  491.             while($pos<$totalLength){
  492.                 // V2.2
  493.                 if($majorVersion<3){
  494.                     // Get frame ID
  495.                     if(!($id=trim(substr($tagData,$pos,3)))) break;
  496.                     // Frame size
  497.                     $unpacked=@unpack('N',"\0".substr($tagData,$pos+3,3));
  498.                     if(!isset($unpacked[1])) break;
  499.                     $size=$unpacked[1];
  500.                     $pos+=6;
  501.                 }
  502.                 // V2.3 & V2.4
  503.                 else{
  504.                     // Get frame ID
  505.                     if(!($id=trim(substr($tagData,$pos,4)))) break;
  506.                     // Frame size
  507.                     $unpacked=@unpack('Nv',substr($tagData,$pos+4,4));
  508.                     if(!isset($unpacked['v'])) break;
  509.                     $size=$unpacked['v'];
  510.                     // Skip flag
  511.                     $pos+=10;
  512.                 }
  513.  
  514.                 // If not filtered tags, skip
  515.                 if($getOnlyTag && $getOnlyTag!=$id) {
  516.                     $pos+=$size;
  517.                     continue;
  518.                 }
  519.  
  520.                 // Get tag data
  521.                 if($process){
  522.                     // Skip binary frames
  523.                     if($id=='APIC'||$id=='GEOB'||$id=='MCDI'||$id=='PRIV') $this->tag[$id]='TRUE';
  524.                     elseif($size>1){
  525.                         if(isset(id3V2Ext::$frames[$id])) $id=id3V2Ext::$frames[$id];
  526.                         $tmpTag=substr($tagData,$pos+1,$size-1);
  527.                         // Corrects comment encoding
  528.                         if(($id=='comment'||$id=='COMM') && ($tmpTag[3]=="\0"||substr($tmpTag,0,3)=='eng')){
  529.                             if(substr($tmpTag,4,1)=='^') $tmpTag=substr($tmpTag,5);
  530.                             elseif(substr($tmpTag,3,1)=="\0") $tmpTag=substr($tmpTag,4);
  531.                             else $tmpTag=substr($tmpTag,3);
  532.                         }
  533.                         // De-UTF16 encode
  534.                         if(substr($tmpTag,0,2)==' ■') $tmpTag=cfCodePageDecode($tmpTag,'UTF-16');
  535.                         if(substr($tmpTag,0,2)=='■ ') $tmpTag=cfUTF8Decode($tmpTag,true,false,false);
  536.                         $tmpTag=str_replace("\0",'',$tmpTag);
  537.                         if(strlen($tmpTag)) $this->tag[$id]=$tmpTag;
  538.                     }
  539.                 }
  540.                 else $this->tag[$id]=substr($tagData,$pos,$size);
  541.                 $pos+=$size;
  542.  
  543.                 if($getOnlyTag) break;
  544.             }
  545.         }
  546.  
  547.         /**
  548.          * Track Length
  549.          * If not set in tags, compute track length by dividing file size by bitrate
  550.          */
  551.         if(!isset($this->tag['length'])){
  552.             $dcc=chr(255);
  553.             if($pos>$this->fileSize) $pos=0;
  554.             if($pos+14>=$this->dataLength) $this->readData(max(READ_BUFFER_SIZE,$pos+4-$this->dataLength));
  555.             $pos=10+$totalLength;
  556.             if(!$this->parseHeader(substr($this->data,$pos,4),$pos)) $pos=0; else $pos--;
  557.  
  558.             // Locate 1st mp3 frame from $pos, in order to find bitrate
  559.             $iter=0; // Max number of iterations, used to prevent hanging on incorrect files
  560.             $nf=0;
  561.  
  562.             while (($vbrParsing || $iter++<2500) && $this->dataLength /* Check data has been read ie. file size not too big */) {
  563.                 // Long chr(255) sequences : skip all tests to find 1st non 255
  564.                 if($nf>100){
  565.                     $nf=0;
  566.                     if(!$this->readData('all')) break; // potential memory problem: abort
  567.                     while ($pos+$nf<$this->fileSize && $this->data[$pos+$nf]==$dcc)    $nf++;
  568.                     if($pos+$nf==$this->fileSize) break(2); else $pos+=$nf-2;
  569.                 }
  570.                 // Over 1e5 attempts, fail
  571.                 if($iter>100000) break;
  572.                 
  573.                 // Search for 11111111 sync sequence
  574.                 $pos=strpos($this->data,$dcc,$pos+1);
  575.  
  576.                 if($pos===false    ){
  577.                     // sync sequence not found: read extra data and restart
  578.                     if($this->dataLength < $this->fileSize){
  579.                         $pos=$this->dataLength;
  580.                         if(!$this->readData()) break; // Refill buffer
  581.                         continue;
  582.                     }
  583.                     // EOF reached, frame not found !
  584.                     break;
  585.                 }
  586.  
  587.                 // Refill buffer if needed
  588.                 if($pos+4>$this->dataLength){
  589.                     if(!$this->readData()) break; // Refill buffer
  590.                     if($pos+4>$this->dataLength) break;
  591.                 }
  592.  
  593.  
  594.                 // Verify it's actually a frame header, and retreive info
  595.                 if(!($h=$this->parseHeader(substr($this->data,$pos,4),$pos))) {$nf++;continue;}
  596.  
  597.                 // Below this point, correct frame found !
  598.                 $nf=0;
  599.                 if($this->firstFramePos===false) $this->firstFramePos=$pos;
  600.                 $this->framesNb++;
  601.                 $this->br+=$h['br'];
  602.  
  603.                 // If not parsing all frames, stop here
  604.                 if(!$vbrParsing) break;
  605.  
  606.                 // Parsing all frames: put whole file in buffer
  607.                 if(!$this->readData('all')) break;
  608.  
  609.                 $firstBr=$h['br'];
  610.  
  611.                 // Parsing all frames: directly jump to next frame
  612.                 while ($pos=$h['nextPos']){
  613.  
  614.                     if(!($h=$this->parseHeader(substr($this->data,$pos,4),$pos))) break;
  615.                     $this->framesNb++;
  616.                     $this->br+=$h['br'];
  617.  
  618.                     if($this->framesNb<=500){
  619.                         if($h['br']!==$firstBr) $this->vbr=1;
  620.                         // If 500 frames parsed and no vbr detected, abort
  621.                         if($this->framesNb==500 && !$this->vbr){
  622.                             $this->framesNb=1;
  623.                             $this->br=$firstBr;
  624.                             break(2);
  625.                         }
  626.                     }
  627.                 }
  628.                 $this->tag['vbr']=$this->vbr;
  629.                 break;
  630.             }
  631.  
  632.             // If at least one frame found
  633.             if($this->framesNb>0){
  634.                 //if($this->framesNb>1) cfVarDump($this->framesNb.' frames');
  635.                 // Average bitrage (bps)
  636.                 $this->tag['br']=$this->br/$this->framesNb;
  637.                 // Track length
  638.                 //cfVarDump('frame found at '.$this->firstFramePos);
  639.                 $this->tag['length']=ceil(8000*($this->fileSize - $this->firstFramePos)/$this->tag['br']); // Set length (ms)
  640.             }
  641.         }
  642.  
  643.         fclose($this->fp);
  644.         $this->data='';
  645.     }
  646.  
  647.     /**
  648.      * @desc See __construct
  649.      *
  650.      * @param string $completeFilename: path and filname
  651.      * @param string $getOnlyTag: (optional) discard all tags not named (tag ID) like this
  652.      * @param boolean $process: (optional) decode tags, replace keys by key names
  653.      * @param boolean $vbrParsing: (optional) true to read all frames to compute track length and bitrate
  654.      * @return boolean: true on success
  655.      */
  656.     function parseFile($completeFilename, $getOnlyTag=false,$process=false,$vbrParsing=false){
  657.         $this->__construct($completeFilename, $getOnlyTag,$process,$vbrParsing);
  658.     }
  659.  
  660.     /**
  661.      * @desc Generate image from embeded data (send headers and image)
  662.      *
  663.      * @param int $width: (optional) output width (if not set, use actual image width)
  664.      * @param int $height: (optional) output height (if not set, use actual image height)
  665.      * @return boolean false if image couldn't be generated
  666.      */
  667.     public function generate($width=0,$height=0){
  668.         foreach ($this->tag as $k=>$v) if($k=='APIC'){
  669.             $pos=0;
  670.  
  671.             if($v[$pos++]!="\0") return false;
  672.             while($v[$pos]!="\0" && $pos<strlen($v))    $pos++;
  673.             $mimeType=substr($v,1,$pos-1);
  674.             $pos+=2;
  675.             while($v[$pos]!="\0" && $pos<strlen($v))    $pos++;
  676.             $pos++;
  677.  
  678.             // Resized image
  679.             if($width && $height){
  680.                 if(!$in=@imagecreatefromstring(substr($v,$pos))){
  681.                     // BMP
  682.                     if(substr($v,$pos,2)=='BM'){
  683.                         require_once(INCLUDE_DIR.'bmp.php');
  684.                         $in=ImageCreateFromBMP(substr($v,$pos),false);
  685.                     }
  686.                 }
  687.  
  688.                 if($in){
  689.                     header('Content-Type: image/jpeg');
  690.                     $out=imagecreatetruecolor($width,$height);
  691.                     fastimagecopyresampled($out,$in,0,0,0,0,$width,$height,imagesx($in),imagesy($in),3);
  692.                     imagejpeg($out,null,70);
  693.                     imagedestroy($in);
  694.                     imagedestroy($out);
  695.                     exit;
  696.                 }
  697.             }
  698.  
  699.             // Original image
  700.             header('Content-Type: '.$mimeType);
  701.             echo substr($v,$pos);
  702.             exit;
  703.         }
  704.     }
  705.  
  706.     /**
  707.      * @desc Tell if an mp3 has an attached picture
  708.      *
  709.      * @return boolean
  710.      */
  711.     public     function hasImage(){return isset($this->tag['APIC']);}
  712.  
  713.     private function get_int32($str){
  714.         $unpacked = unpack('Nv',$str);
  715.         return $unpacked['v'];
  716.     }
  717. }
  718.  
  719.  
  720. /**
  721.  * @desc Extract metadata from FLAC files
  722.  *
  723.  */
  724. class FLACMetaData{
  725.     public $tag;
  726.  
  727.     static $frames=array('title'=>'title','album'=>'album','tracknumber'=>'track','artist'=>'artist','albumartist'=>'band','album artist'=>'band','copyright'=>'copyright','organization'=>'commercial','genre'=>'genre','genre'=>'genre','date'=>'year','isrc'=>'isrc');
  728.  
  729.     function __construct($completeFilename){
  730.         if(!($fp=@fopen($completeFilename,'rb'))) return false;
  731.         // FLAC Header
  732.         if(fread($fp,4)!='fLaC') return false;
  733.         while (1) {
  734.             // Read block header
  735.             $blockHeader=fread($fp,4);
  736.             if(strlen($blockHeader)!=4) break;
  737.             // Last block
  738.             if(ord($blockHeader[0])>127){
  739.                 $blockHeader[0]=chr(ord($blockHeader[0]-128));
  740.                 $lastBlock=true;
  741.             }
  742.             // Block type
  743.             $blockType=ord($blockHeader[0]);
  744.  
  745.             // Block size
  746.             $size=@unpack('N',"\0".substr($blockHeader,1));
  747.             if(!isset($size[1])) break;
  748.             $size=$size[1];
  749.  
  750.             if($blockType==0 || $blockType==4) {
  751.                 $data=fread($fp,$size);
  752.  
  753.                 // METADATA_BLOCK_STREAMINFO
  754.                 if($blockType==0){
  755.                     /* TODO
  756.                     $sr=substr($data,8,2);
  757.                     $sr=unpack('n',$sr);
  758.                     $srl=substr($data,9,1);
  759.                     $srl=$srl>>4;
  760.                     */
  761.                 }
  762.                 // METADATA_BLOCK_VORBIS_COMMENT
  763.                 if($blockType==4){
  764.                     // Skip vendor
  765.                     $subsize=@unpack('V',substr($data,0,4));
  766.                     if(!isset($subsize[1])) break;
  767.                     $pos=4+$subsize[1];
  768.                     $nbComments=unpack('V',substr($data,$pos,4));
  769.                     $pos+=4;
  770.                     // Read comments
  771.                     for($i=0;$i<$nbComments;$i++){
  772.                         $subsize=@unpack('V',substr($data,$pos,4));
  773.                         if(!isset($subsize[1])) break;
  774.                         $comment=substr($data,$pos+4,$subsize[1]);
  775.                         $pos+=4+$subsize[1];
  776.                         @list($k,$v)=explode('=',$comment,2);
  777.                         $k=strtolower($k);
  778.                         if(isset(FLACMetaData::$frames[$k])) $this->tag[FLACMetaData::$frames[$k]]=cfUTF8Decode($v);
  779.                     }
  780.                     // No usefull info out of comments block and streaminfo blocks (and streaminfo is 1st)
  781.                     break;
  782.                 }
  783.             }
  784.             else fseek($fp,$size,SEEK_CUR);
  785.  
  786.             if(isset($lastBlock)) break;
  787.         }
  788.         fclose($fp);
  789.     }
  790. }
  791.  
  792.  
  793. /**
  794.  * @desc Extract metadata from OGG files
  795.  *
  796.  */
  797. class OGGMetaData{
  798.     public $tag;
  799.  
  800.     static $frames=array('title'=>'title','album'=>'album','tracknumber'=>'track','artist'=>'artist','albumartist'=>'band','album artist'=>'band','copyright'=>'copyright','organization'=>'commercial','genre'=>'genre','genre'=>'genre','date'=>'year','isrc'=>'isrc');
  801.  
  802.     function __construct($completeFilename){
  803.         if(!($fp=@fopen($completeFilename,'rb'))) return false;
  804.         $header=fread($fp,4096);
  805.         $pos=0;
  806.         // OGG Header
  807.         if(substr($header,0,4)!='OggS') {fclose($fp);return false;}
  808.         $serial = substr($header, 14, 4);
  809.  
  810.         // Directly search for metadata frame
  811.         if(!($pos=strpos($header,"\x03vorbis"))) {fclose($fp);return false;}
  812.  
  813.         // Read stream description frame
  814.         if($pos=strpos($header,"\x01vorbis")) {
  815.             $this->tag['audioChannels']=ord(substr($header,$pos+7+4,1));
  816.             $this->tag['sampleRate']=unpack('V',substr($header,$pos+7+5,4));
  817.             $this->tag['sampleRate']=$this->tag['sampleRate'][1];
  818.         }
  819.  
  820.         // Directly search for metadata frame
  821.         if(!($pos=strpos($header,"\x03vorbis"))) {fclose($fp);return false;}
  822.  
  823.         // Skip vendor
  824.         $pos+=7;
  825.         $pos+=ord(substr($header,$pos,1))+4;
  826.  
  827.         // Get number of comment fields
  828.         $nbComments=unpack('V',substr($header,$pos,4));
  829.         $pos+=4;
  830.  
  831.         // Read comments
  832.         for($i=0;$i<$nbComments[1];$i++){
  833.             $subsize=@unpack('V',substr($header,$pos,4));
  834.             if(!isset($subsize[1])) break;
  835.             $comment=substr($header,$pos+4,$subsize[1]);
  836.             $pos+=4+$subsize[1];
  837.             @list($k,$v)=explode('=',$comment,2);
  838.             $k=strtolower($k);
  839.             if(isset(FLACMetaData::$frames[$k])) $this->tag[FLACMetaData::$frames[$k]]=cfUTF8Decode($v);
  840.         }
  841.  
  842.         // Get track length (http://people.xiph.org/~giles/2006/meta.php.txt)
  843.         if(isset($this->tag['sampleRate'])){
  844.             fseek($fp, -6000, SEEK_END);
  845.             $end = fread($fp, 6000);
  846.             $tail = strstr($end, "OggS");
  847.             if ($tail) {
  848.               $serial = substr($tail, 14, 4);
  849.               if ($serial == substr($tail, 14, 4)) {
  850.                 $granulepos = ord($tail[6]);
  851.                 $granulepos = $granulepos | (ord($tail[7]) << 8);
  852.                 $granulepos = $granulepos | (ord($tail[8]) << 16);
  853.                 $granulepos = $granulepos | (ord($tail[9]) << 24);
  854.                 $granulepos = $granulepos | (ord($tail[10]) << 32);
  855.                 $granulepos = $granulepos | (ord($tail[11]) << 40);
  856.                 $granulepos = $granulepos | (ord($tail[12]) << 48);
  857.                 $granulepos = $granulepos | (ord($tail[13]) << 56);
  858.                 $this->tag['length'] = ceil(1000*$granulepos/$this->tag['sampleRate']);
  859.               }
  860.             }
  861.         }
  862.  
  863.         fclose($fp);
  864.         return true;
  865.     }
  866. }
  867. ?>